package distributelock

import (
	"context"
	"errors"
	"fmt"
	"path"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/samuel/go-zookeeper/zk"
)

/*
ZooKeeper分布式锁实现原理：
	所有客户端在指定路径下创建临时顺序节点
	获取当前路径下的所有子节点
	判断自己创建的节点是否是最小的序号节点
	如果是，则获取锁；如果不是，则监听前一个节点的删除事件
	完成操作后删除自己的节点释放锁
*/

type ZKLock struct {
	conn     *zk.Conn
	lockPath string   //锁的根路径
	nodePath string   //当前客户端创建的节点路径
	acl      []zk.ACL //访问控制列表
}

func NewZkLock(servers []string, lockPath string) (*ZKLock, error) {
	conn, _, err := zk.Connect(servers, 5*time.Second)
	if err != nil {
		return nil, err
	}

	//创建根节点，如果已经存在则忽略
	exists, _, err := conn.Exists(lockPath)
	if err != nil {
		fmt.Println(err)
		return nil, err
	}
	if !exists {
		_, err = conn.Create(lockPath, []byte(""), 0, zk.WorldACL(zk.PermAll))
		if err != nil {
			return nil, err
		}
	}

	return &ZKLock{
		conn:     conn,
		lockPath: lockPath,
		acl:      zk.WorldACL(zk.PermAll),
	}, nil
}

// 获取锁
func (l *ZKLock) Lock(ctx context.Context) error {
	// 创建临时顺序节点
	prefix := path.Join(l.lockPath, "lock-")
	seqPath, err := l.conn.Create(prefix, []byte(""), zk.FlagEphemeral|zk.FlagSequence, l.acl)
	if err != nil {
		return err
	}
	l.nodePath = seqPath
	seq := path.Base(seqPath) // 更安全的节点名获取方式

	for {
		select {
		case <-ctx.Done():
			return ErrTimeout
		default:
			// 获取并排序子节点
			children, _, err := l.conn.Children(l.lockPath)
			if err != nil {
				continue // 重试
			}

			// 正确排序（按序号数值排序）
			sort.Slice(children, func(i, j int) bool {
				return parseSeqNumber(children[i]) < parseSeqNumber(children[j])
			})

			// 查找当前节点索引
			var currentIndex = -1
			for i, c := range children {
				if c == seq {
					currentIndex = i
					break
				}
			}
			if currentIndex == -1 {
				return errors.New("node disappeared")
			}

			if currentIndex == 0 {
				return nil // 获得锁
			}

			// 监听前一个节点
			prevPath := path.Join(l.lockPath, children[currentIndex-1])
			exists, _, ch, err := l.conn.ExistsW(prevPath)
			if err != nil || !exists {
				continue // 前节点已消失，重新检查
			}

			select {
			case <-ch:
				continue // 前节点释放，重新检查
			case <-ctx.Done():
				return ErrTimeout
			}
		}
	}
}

// 辅助函数：提取节点序号
func parseSeqNumber(node string) int {
	seqStr := strings.TrimPrefix(node, "lock-")
	num, _ := strconv.Atoi(seqStr)
	return num
}

// 释放锁
func (l *ZKLock) Unlock() error {
	//删除当前节点
	if l.nodePath == "" {
		return errors.New("not locked")
	}
	return l.conn.Delete(l.nodePath, -1)
}

// 关闭连接
func (l *ZKLock) Close() {
	l.conn.Close()
}
